/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ECMASCRIPT_ECMA_MACROS_H
#define ECMASCRIPT_ECMA_MACROS_H

#include "plugins/ecmascript/runtime/ecma_macro_accessors.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "utils/logger.h"

#if defined(__cplusplus)

namespace ark::ecmascript {

template <typename T>
static inline T UnalignedLoad(T const *p)
{
    struct Wrapper {
        T x;
    } __attribute__((packed, aligned(1)));
    return reinterpret_cast<Wrapper const *>(p)->x;
}

template <typename T>
static inline void UnalignedStore(T *p, T v)
{
    struct Wrapper {
        T x;
    } __attribute__((packed, aligned(1)));
    reinterpret_cast<Wrapper *>(p)->x = v;
}

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define LOG_ECMA(type) \
    LOG(type, ECMASCRIPT) << __func__ << " Line:" << __LINE__ << " "  // NOLINT(bugprone-lambda-function-name)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ECMA_GC_LOG() LOG(DEBUG, ECMASCRIPT) << " ecmascript gc log: "

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define OPTIONAL_LOG(ecmaVM, level, component) LOG_IF(ecmaVM->IsOptionalLogEnabled(), level, component)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ECMA_BYTRACE_NAME(tag, name) trace::ScopedTrace scopedTrace(name)

/* Note: We can't statically decide the element type is a primitive or heap object, especially for */
/*       dynamically-typed languages like JavaScript. So we simply skip the read-barrier.          */
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define GET_VALUE(addr, offset) Barriers::GetDynValue<JSTaggedType>((addr), (offset))

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define SET_VALUE_WITH_BARRIER(thread, addr, offset, value) \
    ObjectAccessor::SetDynValue(thread, addr, offset, value.GetRawData())

#if !defined(NDEBUG)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define DASSERT(cond) assert(cond)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define DASSERT_PRINT(cond, message)                   \
    if (auto cond_val = cond; UNLIKELY(!(cond_val))) { \
        std::cerr << message << std::endl;             \
        ASSERT(#cond &&cond_val);                      \
    }
#else                                                      // NDEBUG
#define DASSERT(cond) static_cast<void>(0)                 // NOLINT(cppcoreguidelines-macro-usage)
#define DASSERT_PRINT(cond, message) static_cast<void>(0)  // NOLINT(cppcoreguidelines-macro-usage)
#endif                                                     // !NDEBUG

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define RASSERT(cond) assert(cond)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define RASSERT_PRINT(cond, message)                   \
    if (auto cond_val = cond; UNLIKELY(!(cond_val))) { \
        std::cerr << message << std::endl;             \
        RASSERT(#cond &&cond_val);                     \
    }

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define RETURN_IF_ABRUPT_COMPLETION(thread)            \
    do {                                               \
        if (UNLIKELY(thread->HasPendingException())) { \
            return;                                    \
        }                                              \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, value) \
    do {                                                 \
        if (UNLIKELY(thread->HasPendingException())) {   \
            return (value);                              \
        }                                                \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread)  \
    do {                                               \
        if (UNLIKELY(thread->HasPendingException())) { \
            return JSTaggedValue::Exception();         \
        }                                              \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define RETURN_HANDLE_IF_ABRUPT_COMPLETION(type, thread)               \
    do {                                                               \
        if (UNLIKELY(thread->HasPendingException())) {                 \
            return JSHandle<type>(thread, JSTaggedValue::Exception()); \
        }                                                              \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ASSERT_NO_ABRUPT_COMPLETION(thread) ASSERT(!(thread)->HasPendingException());

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, value) \
    do {                                                       \
        if (LIKELY(!thread->HasPendingException())) {          \
            thread->SetException(error);                       \
        }                                                      \
        return (value);                                        \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define THROW_NEW_ERROR_AND_RETURN_EXCEPTION(thread, error) \
    THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define THROW_NEW_ERROR_AND_RETURN(thread, error)     \
    do {                                              \
        if (LIKELY(!thread->HasPendingException())) { \
            thread->SetException(error);              \
        }                                             \
        return;                                       \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define THROW_TYPE_ERROR_AND_RETURN(thread, message, exception)                               \
    do {                                                                                      \
        [[maybe_unused]] EcmaHandleScope error_handle_scope(thread);                          \
        ObjectFactory *objectFactory = thread->GetEcmaVM()->GetFactory();                     \
        JSHandle<JSObject> error = objectFactory->GetJSError(ErrorType::TYPE_ERROR, message); \
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error.GetTaggedValue(), exception);          \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define THROW_TYPE_ERROR(thread, message)                                               \
    do {                                                                                \
        ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();                     \
        JSHandle<JSObject> error = factory->GetJSError(ErrorType::TYPE_ERROR, message); \
        THROW_NEW_ERROR_AND_RETURN(thread, error.GetTaggedValue());                     \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define THROW_RANGE_ERROR_AND_RETURN(thread, message, exception)                               \
    do {                                                                                       \
        ObjectFactory *objectFactory = thread->GetEcmaVM()->GetFactory();                      \
        JSHandle<JSObject> error = objectFactory->GetJSError(ErrorType::RANGE_ERROR, message); \
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error.GetTaggedValue(), exception);           \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define THROW_RANGE_ERROR(thread, message)                                               \
    do {                                                                                 \
        ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();                      \
        JSHandle<JSObject> error = factory->GetJSError(ErrorType::RANGE_ERROR, message); \
        THROW_NEW_ERROR_AND_RETURN(thread, error.GetTaggedValue());                      \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define THROW_URI_ERROR_AND_RETURN(thread, message, exception)                               \
    do {                                                                                     \
        ObjectFactory *objectFactory = thread->GetEcmaVM()->GetFactory();                    \
        JSHandle<JSObject> error = objectFactory->GetJSError(ErrorType::URI_ERROR, message); \
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error.GetTaggedValue(), exception);         \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define THROW_SYNTAX_ERROR_AND_RETURN(thread, message, exception)                         \
    do {                                                                                  \
        ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();                       \
        JSHandle<JSObject> error = factory->GetJSError(ErrorType::SYNTAX_ERROR, message); \
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error.GetTaggedValue(), exception);      \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define RETURN_REJECT_PROMISE_IF_ABRUPT(thread, value, capability)                                                     \
    do {                                                                                                               \
        if ((value).GetTaggedValue().IsCompletionRecord()) {                                                           \
            JSHandle<CompletionRecord> record = JSHandle<CompletionRecord>::Cast(value);                               \
            if (record->IsThrow()) {                                                                                   \
                JSHandle<JSTaggedValue> reject(thread, (capability)->GetReject());                                     \
                auto info =                                                                                            \
                    NewRuntimeCallInfo(thread, reject, JSTaggedValue::Undefined(), JSTaggedValue::Undefined(), 1);     \
                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());                                 \
                info->SetCallArgs(record->GetValue());                                                                 \
                JSTaggedValue res = JSFunction::Call(info.Get());                                                      \
                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, res);                                                        \
                return (capability)->GetPromise();                                                                     \
            }                                                                                                          \
        }                                                                                                              \
        if (UNLIKELY((thread)->HasPendingException())) {                                                               \
            (thread)->ClearException();                                                                                \
            JSHandle<JSTaggedValue> reject(thread, (capability)->GetReject());                                         \
            auto info = NewRuntimeCallInfo(thread, reject, JSTaggedValue::Undefined(), JSTaggedValue::Undefined(), 1); \
            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());                                     \
            info->SetCallArgs(value.GetTaggedValue());                                                                 \
            JSTaggedValue res = JSFunction::Call(info.Get());                                                          \
            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, res);                                                            \
            return (capability)->GetPromise();                                                                         \
        }                                                                                                              \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define RETURN_REJECT_PROMISE_IF_ABRUPT_THROWN_VALUE(thread, value, capability) \
    if (UNLIKELY(thread->HasPendingException())) {                              \
        value = JSPromise::IfThrowGetThrowValue(thread);                        \
    }                                                                           \
    RETURN_REJECT_PROMISE_IF_ABRUPT(thread, value, capability)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define RETURN_COMPLETION_IF_ABRUPT(thread, value)                            \
    do {                                                                      \
        if (UNLIKELY(thread->HasPendingException())) {                        \
            JSHandle<CompletionRecord> completionRecord =                     \
                factory->NewCompletionRecord(CompletionRecord::THROW, value); \
            return (completionRecord);                                        \
        }                                                                     \
    } while (false)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define DECL_DUMP()                                                    \
    void Dump(JSThread *thread, std::ostream &os) const DUMP_API_ATTR; \
    void Dump(JSThread *thread) const DUMP_API_ATTR                    \
    {                                                                  \
        Dump(thread, std::cout);                                       \
    }                                                                  \
    void DumpForSnapshot(JSThread *thread, std::vector<std::pair<PandaString, JSTaggedValue>> &vec) const;

#endif  // defined(__cplusplus)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define DECL_CAST(TYPE)                           \
    static TYPE *Cast(ObjectHeader *object)       \
    {                                             \
        ASSERT(JSTaggedValue(object).Is##TYPE()); \
        return reinterpret_cast<TYPE *>(object);  \
    }

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define DECL_VISIT_ARRAY(BEGIN_OFFSET, LENGTH)                                                                \
    void VisitRangeSlot(const EcmaObjectRangeVisitor &visitor)                                                \
    {                                                                                                         \
        size_t endOffset = (BEGIN_OFFSET) + (LENGTH)*JSTaggedValue::TaggedTypeSize();                         \
        visitor(this, ObjectSlot(ToUintPtr(this) + (BEGIN_OFFSET)), ObjectSlot(ToUintPtr(this) + endOffset)); \
    }

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define DECL_VISIT_OBJECT(BEGIN_OFFSET, END_OFFSET)                                                              \
    void VisitRangeSlot(const EcmaObjectRangeVisitor &visitor)                                                   \
    {                                                                                                            \
        visitor(this, ObjectSlot(ToUintPtr(this) + (BEGIN_OFFSET)), ObjectSlot(ToUintPtr(this) + (END_OFFSET))); \
    }

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define DECL_VISIT_OBJECT_FOR_JS_OBJECT(PARENTCLASS, BEGIN_OFFSET, END_OFFSET)                                   \
    void VisitRangeSlot(const EcmaObjectRangeVisitor &visitor)                                                   \
    {                                                                                                            \
        VisitObjects(visitor);                                                                                   \
        /* visit in object fields */                                                                             \
        auto objSize = this->GetClass()->GetObjectSize();                                                        \
        if (objSize > (END_OFFSET)) {                                                                            \
            visitor(this, ObjectSlot(ToUintPtr(this) + (END_OFFSET)), ObjectSlot(ToUintPtr(this) + objSize));    \
        }                                                                                                        \
    }                                                                                                            \
    void VisitObjects(const EcmaObjectRangeVisitor &visitor)                                                     \
    {                                                                                                            \
        PARENTCLASS::VisitObjects(visitor);                                                                      \
        if ((BEGIN_OFFSET) == (END_OFFSET)) {                                                                    \
            return;                                                                                              \
        }                                                                                                        \
        visitor(this, ObjectSlot(ToUintPtr(this) + (BEGIN_OFFSET)), ObjectSlot(ToUintPtr(this) + (END_OFFSET))); \
    }

#if ECMASCRIPT_ENABLE_CAST_CHECK
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CAST_CHECK(CAST_TYPE, CHECK_METHOD)                              \
    static inline CAST_TYPE *Cast(ObjectHeader *object)                  \
    {                                                                    \
        if (!JSTaggedValue(object).CHECK_METHOD()) {                     \
            std::abort();                                                \
        }                                                                \
        return static_cast<CAST_TYPE *>(object);                         \
    }                                                                    \
    static inline const CAST_TYPE *ConstCast(const ObjectHeader *object) \
    {                                                                    \
        if (!JSTaggedValue(object).CHECK_METHOD()) {                     \
            std::abort();                                                \
        }                                                                \
        return static_cast<const CAST_TYPE *>(object);                   \
    }
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CAST_CHECK(CAST_TYPE, CHECK_METHOD)                              \
    static inline CAST_TYPE *Cast(ObjectHeader *object)                  \
    {                                                                    \
        ASSERT(JSTaggedValue(object).CHECK_METHOD());                    \
        return static_cast<CAST_TYPE *>(object);                         \
    }                                                                    \
    static const inline CAST_TYPE *ConstCast(const ObjectHeader *object) \
    {                                                                    \
        ASSERT(JSTaggedValue(object).CHECK_METHOD());                    \
        return static_cast<const CAST_TYPE *>(object);                   \
    }
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CAST_NO_CHECK(CAST_TYPE)                                         \
    static inline CAST_TYPE *Cast(ObjectHeader *object)                  \
    {                                                                    \
        return static_cast<CAST_TYPE *>(object);                         \
    }                                                                    \
    static const inline CAST_TYPE *ConstCast(const ObjectHeader *object) \
    {                                                                    \
        return static_cast<const CAST_TYPE *>(object);                   \
    }
#endif

#if ECMASCRIPT_ENABLE_CAST_CHECK
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CAST_CHECK_TAGGEDVALUE(CAST_TYPE, CHECK_METHOD)                                    \
    static inline CAST_TYPE *Cast(JSTaggedValue value)                                     \
    {                                                                                      \
        if (value.IsHeapObject() && value.GetTaggedObject()->GetClass()->CHECK_METHOD()) { \
            return static_cast<CAST_TYPE *>(value.GetTaggedObject());                      \
        }                                                                                  \
        std::abort();                                                                      \
    }
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CAST_CHECK_TAGGEDVALUE(CAST_TYPE, CHECK_METHOD)                                      \
    static inline CAST_TYPE *Cast(JSTaggedValue value)                                       \
    {                                                                                        \
        ASSERT(value.IsHeapObject() && value.GetTaggedObject()->GetClass()->CHECK_METHOD()); \
        return static_cast<CAST_TYPE *>(value.GetTaggedObject());                            \
    }
#endif

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CHECK_DUMP_FILEDS(begin, end, num)                                         \
    LOG_IF(num != (end - begin) / JSTaggedValue::TaggedTypeSize(), FATAL, RUNTIME) \
        << "Fileds in obj are not in dump list. ";

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CHECK_OBJECT_SIZE(size)                                                                   \
    if (size == 0) {                                                                              \
        LOG(FATAL, ECMASCRIPT) << __func__ << " Line: " << __LINE__ << " objectSize is " << size; \
    }

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CHECK_REGION_END(begin, end)                                                                           \
    if (begin > end) {                                                                                         \
        LOG(FATAL, ECMASCRIPT) << __func__ << " Line: " << __LINE__ << " begin: " << begin << " end: " << end; \
    }

}  // namespace ark::ecmascript

#endif  // ECMASCRIPT_ECMA_MACROS_H
